﻿namespace Contoso.ActiveDirectory {

    #region Usings
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.IO.Compression;
    using System.Linq;
    using System.Reflection;
    using System.Runtime.CompilerServices;
    using System.Security;
    using System.Text;
    using System.Threading.Tasks; 
    #endregion

    internal static class CustomExtensions {

        /// <summary>
        /// Provides a task-based mechanism for copying data from one stream to another
        /// </summary>
        /// <remarks>Stream.ReadAsync() is new to .NET 4.5</remarks>
        internal static async Task CopyToAsync(this Stream source, Stream destination) {
            var buffer = new byte[0x1000];
            int bytesRead;
            while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length)) > 0) {
                await destination.WriteAsync(buffer, 0, bytesRead);
            }

            // optimized - uses overlapped read/write latencies
            //    int i = 0;
            //    var buffers = new [] { new byte[0x1000], new byte[0x1000] };
            //    Task writeTask = null;
            //    while(true)
            //    {
            //        var readTask = source.ReadAsync(buffers[i], 0, buffers[i].Length))>0;
            //        if (writeTask != null) await Task.WhenAll(readTask, writeTask);
            //        int bytesRead = await readTask;
            //        if (bytesRead == 0) break;
            //        writeTask = destination.WriteAsync(buffers[i], 0, bytesRead);
            //        i ^= 1; // swap buffers
            //    }
        }

        [DebuggerStepThroughAttribute]
        [MethodImpl(MethodImplOptions.NoOptimization)]
        internal static string CurrentMethodName() {
            var frame = new StackFrame(1);
            var method = frame.GetMethod();
            var type = method.DeclaringType;
            var name = method.Name;

            return type + "::" + name + "(): ";
        }

        /// <summary>
        /// method for returning N number of random items from a generic list
        /// </summary>
        /// <typeparam name="T">Item type</typeparam>
        /// <param name="list">Generic list we wish to retrieve from</param>
        /// <param name="count">number of items to return</param>
        /// <param name="exclusions">items to exclude from returning</param>
        /// <returns></returns>
        [DebuggerStepThroughAttribute]
        internal static IEnumerable<T> Randomize<T>(this List<T> list, int count = 1, List<T> exclusions = null) {
            var randomList = new List<T>();
            var random = new Random(Guid.NewGuid().GetHashCode());
            if (exclusions == null) {
                exclusions = new List<T>();
            }

            while (list.Where(x => !exclusions.Contains(x)).ToList().Count > 0) {
                //get the next random number between 0 and the list count
                int index = random.Next(0, list.Where(x => !exclusions.Contains(x)).ToList().Count);

                //get that index
                randomList.Add(list[index]);

                //remove that item so it cant be added again
                list.RemoveAt(index);
            }

            //return the specified number of items
            return randomList.Take(count);
        }

        /// <summary>
        /// Remove the line wraps from LDIF list output
        /// </summary>
        [DebuggerStepThroughAttribute]
        internal static List<string> ReformattedLdifList(this List<string> list) {
            var reformattedList = new List<string>();
            int reformattedListIndex = 0;

            string line = string.Empty;
            for (int index = 0; index < list.Count; index++) {
                // if the line starts with a space, it is a continuation from the previous line that was wrapped
                if (list[index].StartsWith(" ") && list[index].Length > 1) {
                    if (reformattedList.Count == reformattedListIndex) {
                        reformattedList[reformattedListIndex - 1] += list[index].Substring(1);
                    }
                }
                else {
                    reformattedList.Add(list[index]);
                    reformattedListIndex++;
                }
            }

            return reformattedList;
        }

        internal static void ShallowCopy<TParent, TChild>(this TParent parent, TChild child) {
            foreach (PropertyInfo property in parent.GetType().GetProperties()) {
                if (property.CanWrite) {
                    property.SetValue(child, property.GetValue(parent, null), null);
                }
            }
        }

        /// <summary>
        /// Stack trace, target site, and error message of outer and inner exception, formatted with newlines
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="exception"></param>
        /// <returns></returns>
        [DebuggerStepThroughAttribute]
        internal static string VerboseExceptionString<T>(this T exception) where T : Exception {
            var exceptionString = new StringBuilder();
            var aggregateException = exception as AggregateException;

            exceptionString.AppendLine(string.Format(" Exception: {0} Message: {1}", exception.GetType().Name, exception.Message != null ? exception.Message : "NULL"));
            exceptionString.AppendLine(string.Format(" StackTrace: {0}", exception.StackTrace != null ? exception.StackTrace : "NULL"));
            exceptionString.AppendLine(string.Format(" TargetSite: {0}", exception.TargetSite != null ? exception.TargetSite.ToString() : "NULL"));

            if (exception.InnerException != null) {
                exceptionString.AppendLine();
                exceptionString.AppendLine("Inner Exception:");
                exceptionString.AppendLine(exception.InnerException.VerboseExceptionString());
            }

            if (aggregateException != null) {
                if (aggregateException.InnerExceptions != null) {
                    foreach (var innerException in aggregateException.InnerExceptions) {
                        exceptionString.AppendLine();
                        exceptionString.AppendLine("Aggregate Exception Inner Exception:");
                        exceptionString.AppendLine(innerException.VerboseExceptionString());
                    }
                }
            }

            return exceptionString.ToString();
        }

        #region Byte Array Extensions
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times"), DebuggerStepThroughAttribute]
        internal static byte[] ToCompressedByteArray(this string stringToCompress) {
            byte[] compressedData = null;
            byte[] uncompressedData = Encoding.Unicode.GetBytes(stringToCompress);

            using (var compressedMemoryStream = new MemoryStream()) {
                using (var deflateStream = new DeflateStream(compressedMemoryStream, CompressionMode.Compress, leaveOpen: true)) {
                    deflateStream.Write(uncompressedData, 0, uncompressedData.Length);
                }

                compressedMemoryStream.Position = 0;
                compressedData = compressedMemoryStream.ToArray();
            }

            return compressedData;
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times"), DebuggerStepThroughAttribute]
        internal static string FromCompressedByteArray(this byte[] compressedData, Encoding targetEncoding = null) {
            var decompressedText = string.Empty;
            if (targetEncoding == null) {
                targetEncoding = Encoding.Unicode;
            }

            using (var memoryStreamInput = new MemoryStream(compressedData))
            using (var memoryStreamOutput = new MemoryStream()) {
                using (var deflateStream = new DeflateStream(memoryStreamInput, CompressionMode.Decompress, leaveOpen: true)) {
                    deflateStream.CopyTo(memoryStreamOutput);
                }

                decompressedText = targetEncoding.GetString(memoryStreamOutput.ToArray());
            }

            return decompressedText;
        }
        #endregion

        #region String formatting
        /// <summary>
        /// Converts a Base64 string into a Byte Array
        /// </summary>
        /// <param name="base64String">Base64 Encoded String</param>
        /// <returns>Byte Array</returns>
        [DebuggerStepThroughAttribute]
        internal static byte[] FromBase64(this string base64String) {
            return Convert.FromBase64String(base64String);
        }

        /// <summary>
        /// Returns the Contents of a Secure String
        /// </summary>
        /// <param name="securedString">Secure String</param>
        /// <returns>Normal String</returns>
        [DebuggerStepThroughAttribute]
        internal static string ReadString(this SecureString securedString) {
            if (securedString == null) return string.Empty;
            IntPtr ptr = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(securedString);
            return System.Runtime.InteropServices.Marshal.PtrToStringUni(ptr);
        }

        /// <summary>
        /// Removes control characters from text and replaces with a space
        /// </summary>
        /// <param name="text">The string to normalize</param>
        /// <returns>Normalized text string</returns>
        [DebuggerStepThroughAttribute]
        internal static string RemoveControlCharacters(this string text) {
            if (string.IsNullOrWhiteSpace(text)) return string.Empty;
            var normalizedText = new StringBuilder();
            for (int index = 0; index < text.Length; index++) {
                if (char.IsControl(text[index])) {
                    normalizedText.Append(" ");
                }
                else {
                    normalizedText.Append(text[index]);
                }
            }

            return normalizedText.ToString();
        }

        /// <summary>
        /// Converts a Byte Array into a Base64 Encoded String
        /// </summary>
        /// <param name="byteArray">Byte Array</param>
        /// <returns>Base64 Encoded String</returns>
        [DebuggerStepThroughAttribute]
        internal static string ToBase64(this byte[] byteArray) {
            return Convert.ToBase64String(byteArray);
        }

        /// <summary>
        /// Converts a list of strings to a delimited string
        /// </summary>
        /// <param name="list">The list of strings</param>
        /// <param name="separator">The separator string</param>
        /// <returns>The delimited string</returns>
        [DebuggerStepThroughAttribute]
        internal static string ToDelimitedString(this IEnumerable<string> list, string separator = ";") {
            if (list == null)  return string.Empty;
            return string.Join(separator, list);
        }

        /// <summary>
        /// Gets a newline-formatted string for a collection
        /// </summary>
        /// <typeparam name="T">The collection type</typeparam>
        /// <param name="list">The collection</param>
        /// <returns>The newline-formatted string</returns>
        [DebuggerStepThroughAttribute]
        public static string ToFormattedString<T>(this IEnumerable<T> list) {
            if (list == null) return string.Empty;
            return string.Join(Environment.NewLine, list);
        }

        /// <summary>
        /// When querying for objectGuid=, ensures that the guid string is encoded
        /// </summary>
        [DebuggerStepThroughAttribute]
        internal static string ToOctectString(this Guid guid) {
            byte[] byteGuid = guid.ToByteArray();
            var queryGuid = new StringBuilder();
            foreach (byte b in byteGuid) {
                queryGuid.AppendFormat(@"\{0}", b.ToString("x2"));
            }

            return queryGuid.ToString();
        }

        /// <summary>
        /// Creates a Secure String
        /// </summary>
        /// <param name="plainText">Non Secured String</param>
        /// <returns>SecureString</returns>
        [DebuggerStepThroughAttribute]
        internal static SecureString ToSecureString(this string plainText) {
            var secureString = new SecureString();
            foreach (Char characterToSecure in plainText.ToCharArray()) {
                secureString.AppendChar(characterToSecure);
            }
            secureString.MakeReadOnly();
            return secureString;
        } 
        #endregion

        #region DateTime Extensions
        [DebuggerStepThroughAttribute]
        public static string YMDFriendly(this DateTime dateTime) {
            return dateTime.ToString("yyyy-MM-dd");
        }

        [DebuggerStepThroughAttribute]
        public static string YMDHMFriendly(this DateTime dateTime) {
            return dateTime.ToString("yyyy-MM-dd HH:mm");
        }

        [DebuggerStepThroughAttribute]
        public static string YMDHMSFriendly(this DateTime dateTime) {
            return dateTime.ToString("yyyy-MM-dd HH:mm:ss");
        }
        #endregion

        #region TimeSpan Extensions
        [DebuggerStepThroughAttribute]
        internal static string DHMSFriendlyString(this TimeSpan timeSpan) {
            return string.Format("{0:00}.{1:00}:{2:00}:{3:00}", timeSpan.Days, timeSpan.Hours, timeSpan.Minutes, timeSpan.Seconds);
        }

        [DebuggerStepThroughAttribute]
        internal static string HMSFriendlyString(this TimeSpan timeSpan) {
            return string.Format("{0:00}:{1:00}:{2:00}", timeSpan.Hours, timeSpan.Minutes, timeSpan.Seconds);
        } 
        #endregion

    }
}
